<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon.jpg">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon.jpg">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"matian.connorma.cn","root":"/","scheme":"Muse","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="在定位线上问题时，经常需要对JVM内存情况进行分析。在java类加载器问题思考与简单模拟热部署中为了查看类代理生成情况，介绍了图形化工具HSDB的使用，而生产上进行分析更多的是使用jmap、jstat等工具，在java 8又提供了jcmd这一新的升级版工具；查看gc日志可以了解到详细的GC信息；linux提供了GDB、trace、perf等调试利器 然而，jmap等工具有着一些难以克服的问题：需要">
<meta property="og:type" content="article">
<meta property="og:title" content="jvm内存及GC分析工具">
<meta property="og:url" content="http://matian.connorma.cn/b2a2bcde.html">
<meta property="og:site_name" content="马天">
<meta property="og:description" content="在定位线上问题时，经常需要对JVM内存情况进行分析。在java类加载器问题思考与简单模拟热部署中为了查看类代理生成情况，介绍了图形化工具HSDB的使用，而生产上进行分析更多的是使用jmap、jstat等工具，在java 8又提供了jcmd这一新的升级版工具；查看gc日志可以了解到详细的GC信息；linux提供了GDB、trace、perf等调试利器 然而，jmap等工具有着一些难以克服的问题：需要">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2022-12-12T01:30:54.000Z">
<meta property="article:modified_time" content="2024-11-12T13:25:51.511Z">
<meta property="article:author" content="马天">
<meta property="article:tag" content="Java">
<meta property="article:tag" content="JVM">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://matian.connorma.cn/b2a2bcde.html">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>jvm内存及GC分析工具 | 马天</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">马天</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://matian.connorma.cn/b2a2bcde.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="马天">
      <meta itemprop="description" content="程序猿">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="马天">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          jvm内存及GC分析工具
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-12-12 09:30:54" itemprop="dateCreated datePublished" datetime="2022-12-12T09:30:54+08:00">2022-12-12</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>在定位线上问题时，经常需要对JVM内存情况进行分析。在<a href="/94439062.html" title="java类加载器问题思考与简单模拟热部署">java类加载器问题思考与简单模拟热部署</a>中为了查看类代理生成情况，介绍了图形化工具HSDB的使用，而生产上进行分析更多的是使用jmap、jstat等工具，在java 8又提供了jcmd这一新的升级版工具；查看gc日志可以了解到详细的GC信息；linux提供了GDB、trace、perf等调试利器</p>
<p>然而，jmap等工具有着一些难以克服的问题：需要JVM pause导致服务暂停；开发人员需要登录服务器操作（虽然可以连接远程server，但也需要开放网络端口）；修改代码、测试需要重新打包发布。<a target="_blank" rel="noopener" href="https://arthas.aliyun.com/doc/">Arthas</a>是阿里开源的线上监控诊断产品，克服了这些缺点，支持在线解决生产问题，无需JVM重启，无需代码更改，不会暂停正在运行的线程</p>
<p>不得不提的是，不管是jmap、jstat，又或者arthas，在生产环境直接使用都是有风险的。内存的dump、线程堆栈的获取，不得不暂停JVM，即使这个过程很快，也难以完全避免服务中断；部分操作可能引起JVM崩溃，影响稳定性；等等。如何线上排查？首先的大原则是，解决问题优先于定位原因，只有在解决问题且有条件的情况下才应该扎进去定位原因；然后，根据部署情况评估是否要切走问题进程的流量后再进行排查；保护现场，尽可能留存日志、线程堆栈、内存dump等信息；想办法复现验证</p>
<span id="more"></span>

<p>以下的示例都在java 8进行</p>
<h2 id="jmap的使用"><a href="#jmap的使用" class="headerlink" title="jmap的使用"></a>jmap的使用</h2><p>jmap最常用的功能是参看JVM堆内存情况和导出JVM的内存dump</p>
<p>测试的java进程启动时使用的参数为”-Xms512m -Xmx512m -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode”，其他使用默认参数；默认NewRatio&#x3D;2，我们可以算出，老年代最大为512&#x2F;(1+2)*2约341MB，年轻代则为约170MB；而默认的SurvivorRatio&#x3D;8，因此Eden区最大约为136MB，两个Survivor区最大约为17MB</p>
<p>attach到JVM进程并输出堆内存情况（也可以指定分析一个dump文件）</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmap -heap &lt;pid&gt;</span><br></pre></td></tr></table></figure>
<p>输出类似：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">Attaching to process ID 3629, please wait...</span><br><span class="line">Debugger attached successfully.</span><br><span class="line">Server compiler detected.</span><br><span class="line">JVM version is 25.351-b10</span><br><span class="line"></span><br><span class="line">using parallel threads in the new generation.</span><br><span class="line">using thread-local object allocation.</span><br><span class="line">Concurrent Mark-Sweep GC</span><br><span class="line"></span><br><span class="line">Heap Configuration:</span><br><span class="line">   MinHeapFreeRatio         = 40</span><br><span class="line">   MaxHeapFreeRatio         = 70</span><br><span class="line">   MaxHeapSize              = 536870912 (512.0MB)</span><br><span class="line">   NewSize                  = 178913280 (170.625MB)</span><br><span class="line">   MaxNewSize               = 178913280 (170.625MB)</span><br><span class="line">   OldSize                  = 357957632 (341.375MB)</span><br><span class="line">   NewRatio                 = 2</span><br><span class="line">   SurvivorRatio            = 8</span><br><span class="line">   MetaspaceSize            = 21807104 (20.796875MB)</span><br><span class="line">   CompressedClassSpaceSize = 1073741824 (1024.0MB)</span><br><span class="line">   MaxMetaspaceSize         = 17592186044415 MB</span><br><span class="line">   G1HeapRegionSize         = 0 (0.0MB)</span><br><span class="line"></span><br><span class="line">Heap Usage:</span><br><span class="line">New Generation (Eden + 1 Survivor Space):</span><br><span class="line">   capacity = 161021952 (153.5625MB)</span><br><span class="line">   used     = 85698208 (81.72817993164062MB)</span><br><span class="line">   free     = 75323744 (71.83432006835938MB)</span><br><span class="line">   53.22144399292837% used</span><br><span class="line">Eden Space:</span><br><span class="line">   capacity = 143130624 (136.5MB)</span><br><span class="line">   used     = 85230712 (81.28234100341797MB)</span><br><span class="line">   free     = 57899912 (55.21765899658203MB)</span><br><span class="line">   59.54750256660657% used</span><br><span class="line">From Space:</span><br><span class="line">   capacity = 17891328 (17.0625MB)</span><br><span class="line">   used     = 467496 (0.44583892822265625MB)</span><br><span class="line">   free     = 17423832 (16.616661071777344MB)</span><br><span class="line">   2.6129754035027473% used</span><br><span class="line">To Space:</span><br><span class="line">   capacity = 17891328 (17.0625MB)</span><br><span class="line">   used     = 0 (0.0MB)</span><br><span class="line">   free     = 17891328 (17.0625MB)</span><br><span class="line">   0.0% used</span><br><span class="line">concurrent mark-sweep generation:</span><br><span class="line">   capacity = 357957632 (341.375MB)</span><br><span class="line">   used     = 53575856 (51.09391784667969MB)</span><br><span class="line">   free     = 304381776 (290.2810821533203MB)</span><br><span class="line">   14.967094206277462% used</span><br></pre></td></tr></table></figure>

<p>从输出中可以理解到JVM使用的GC算法、各个区域的大小和使用情况</p>
<p>可以使用以下命令attach到一个JVM进程并dump内存到指定文件core.dump</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmap -dump:format=b,file=core.dump &lt;pid&gt;</span><br></pre></td></tr></table></figure>
<p>如果无响应，可以指定-F强制导出；导出的dump文件可以使用jhat、jprofiler等工具进行分析。dump也可以指定:live参数只输出活对象</p>
<p>使用histo参数可以attach到JVM进程（也可以指定dump文件），查看每个class的实例数、占用情况，使用:live可以指定只查看存活的对象</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmap -histo:live &lt;pid&gt;</span><br></pre></td></tr></table></figure>

<p>输出包括实例数、大小和类名，类似：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> num     #instances         #bytes  class name</span><br><span class="line">----------------------------------------------</span><br><span class="line">   1:        126683       11803240  [C</span><br><span class="line">   2:         35038        3083344  java.lang.reflect.Method</span><br><span class="line">   3:        126242        3029808  java.lang.String</span><br><span class="line">   4:         71742        2295744  java.util.concurrent.ConcurrentHashMap$Node</span><br><span class="line">   5:         38433        2149048  [Ljava.lang.Object;</span><br><span class="line">   6:          9809        1950192  [B</span><br><span class="line">   7:         17211        1927080  java.lang.Class</span><br><span class="line">   8:          8684        1588624  [I</span><br><span class="line">   9:         32634        1305360  java.util.LinkedHashMap$Entry</span><br><span class="line">  10:         12941        1056896  [Ljava.util.HashMap$Node;</span><br><span class="line">  11:         26644         852608  java.util.HashMap$Node</span><br><span class="line">  12:         13122         734832  java.util.LinkedHashMap</span><br><span class="line">  13:         29041         696984  java.util.ArrayList</span><br><span class="line">  14:         38200         611200  java.lang.Object</span><br><span class="line">  15:           512         571152  [Ljava.util.concurrent.ConcurrentHashMap$Node;</span><br><span class="line">  16:         21041         472888  [Ljava.lang.Class;</span><br><span class="line">  17:          7932         380736  java.util.HashMap</span><br></pre></td></tr></table></figure>
<p>histo参数导出的信息可以让我们了解到heap的使用情况分布；间隔一段时间多次导出histo的信息进行对比，还可以观察到对象实例分配的变化情况</p>
<p>使用clstats参数可以查看方法区相关的类加载器的统计信息：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmap -clstats &lt;pid&gt;</span><br></pre></td></tr></table></figure>

<p>输出类似：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">class_loader	classes	bytes	parent_loader	alive?	type</span><br><span class="line"></span><br><span class="line">&lt;bootstrap&gt;	3519	6119418	  null  	live	&lt;internal&gt;</span><br><span class="line">0x00000000ecd144d0	1	880	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed591958	1	1473	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000eb02df00	1	1471	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ecb28cb0	1	1474	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed56e150	1	880	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed07e100	1	1475	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ecd154d8	1	880	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ecb29cb8	1	882	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed1a6d10	1	1473	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed224928	1	880	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed520558	1	880	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed567958	1	880	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ed56bd58	1	1473	0x00000000eaaa0000	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br><span class="line">0x00000000ecc740c8	1	880	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000100009e78</span><br></pre></td></tr></table></figure>
<p>输出包括类加载器、加载类的数量、类元数据大小、父加载器、存活状态等</p>
<p>另，为java进程添加以下参数可以在JVM出现OOM崩溃时自动生成dump文件：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./dump.hprof</span><br></pre></td></tr></table></figure>

<h2 id="jstat的使用"><a href="#jstat的使用" class="headerlink" title="jstat的使用"></a>jstat的使用</h2><p>jstat用于查看堆内存及GC信息，支持的参数较多，可以参考<a target="_blank" rel="noopener" href="https://blog.csdn.net/wangzhongshun/article/details/112545871">网友的总结</a></p>
<p>最常用的是-gc和-gcutil参数</p>
<p>-gc将输出堆内存各个区域的使用量和最大容量，以及YoungGC、FullGC次数及耗时</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jstat -gc &lt;pid&gt; &lt;interval&gt; &lt;count&gt;</span><br></pre></td></tr></table></figure>
<p>例如一下参数将每500ms输出一次、共输出5次</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jstat -gc &lt;pid&gt; 500 5</span><br></pre></td></tr></table></figure>

<p>输出类似：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   </span><br><span class="line">17472.0 17472.0  0.0   566.0  139776.0 26040.1   349568.0   45522.1   101792.0 94752.5 12224.0 11003.1    143    0.734   9      0.594    1.328</span><br><span class="line">17472.0 17472.0  0.0   566.0  139776.0 26040.1   349568.0   45522.1   101792.0 94752.5 12224.0 11003.1    143    0.734   9      0.594    1.328</span><br><span class="line">17472.0 17472.0  0.0   566.0  139776.0 26040.1   349568.0   45522.1   101792.0 94752.5 12224.0 11003.1    143    0.734   9      0.594    1.328</span><br><span class="line">17472.0 17472.0  0.0   566.0  139776.0 26040.1   349568.0   45522.1   101792.0 94752.5 12224.0 11003.1    143    0.734   9      0.594    1.328</span><br><span class="line">17472.0 17472.0  0.0   566.0  139776.0 26040.1   349568.0   45522.1   101792.0 94752.5 12224.0 11003.1    143    0.734   9      0.594    1.328</span><br></pre></td></tr></table></figure>

<p>输出的列中，后缀为U代表Used使用量（KB），C代表Capacity最大容量（KB）；S0表示Survivor 0区域，同理S1表示Survivor 1区域；E表示Eden区；O表示Old区即老年代；M表示Meta区即方法区（元数据区）；CCS表示Compressed Class Space即压缩类空间（class指针压缩相关）；YGC表示YoungGC次数，YCGT表示YoundGC耗时（秒），FGC、FGCT及GCT同理</p>
<p>-gcutil的作用与-gc类似，不过输出的是百分比而不是使用量：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jstat -gcutil &lt;pid&gt; 500 5</span><br></pre></td></tr></table></figure>

<p>输出类似：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   </span><br><span class="line">0.00   3.24  50.83  13.02  93.08  90.01    143    0.734     9    0.594    1.328</span><br><span class="line">0.00   3.24  50.83  13.02  93.08  90.01    143    0.734     9    0.594    1.328</span><br><span class="line">0.00   3.24  50.83  13.02  93.08  90.01    143    0.734     9    0.594    1.328</span><br></pre></td></tr></table></figure>

<p>通过对比多条输出中各区域的变化情况及GC次数，可以猜测GC问题所在</p>
<h2 id="GC日志分析"><a href="#GC日志分析" class="headerlink" title="GC日志分析"></a>GC日志分析</h2><p>关于GC log的分析，主要学习自<a target="_blank" rel="noopener" href="https://www.zhihu.com/question/558845796/answer/3249138728">这篇文章</a>，结合了一部分个人经历</p>
<p>在java 8中使用一下参数开启GC日志输出：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:./logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=8 -XX:GCLogFileSize=8m</span><br></pre></td></tr></table></figure>

<p>其中，-Xloggc指定gc log文件位置，UseGCLogFileRotation、NumberOfGCLogFiles、GCLogFileSize用于配置日志滚动</p>
<p>使用一下参数启动java测试进程：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Xms200m -Xmx200m -Xmn100m -XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:./gc.log -jar xxx.jar</span><br></pre></td></tr></table></figure>

<p>gc log如：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">2024-06-06T20:39:22.711+0800: 1.227: [GC (Allocation Failure) 2024-06-06T20:39:22.711+0800: 1.228: [ParNew: 81920K-&gt;9789K(92160K), 0.0200098 secs] 81920K-&gt;28238K(194560K), 0.0205668 secs] [Times: user=0.03 sys=0.05, real=0.02 secs]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">2024-06-06T20:39:23.011+0800: 1.527: [Full GC (Allocation Failure) 2024-06-06T20:39:23.011+0800: 1.527: [CMS: 101869K-&gt;101867K(102400K), 0.0085520 secs] 192034K-&gt;186908K(194560K), [Metaspace: 4052K-&gt;4052K(1056768K)</span><br><span class="line">], 0.0086524 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] </span><br><span class="line"></span><br><span class="line"></span><br><span class="line">2024-06-06T20:39:22.793+0800: 1.310: [GC (CMS Initial Mark) [1 CMS-initial-mark: 88612K(102400K)] 100973K(194560K), 0.0013844 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] </span><br><span class="line">2024-06-06T20:39:22.795+0800: 1.311: [CMS-concurrent-mark-start]</span><br><span class="line">2024-06-06T20:39:22.796+0800: 1.312: [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] </span><br><span class="line">2024-06-06T20:39:22.796+0800: 1.312: [CMS-concurrent-preclean-start]</span><br><span class="line">2024-06-06T20:39:22.796+0800: 1.313: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] </span><br><span class="line">2024-06-06T20:39:22.796+0800: 1.313: [CMS-concurrent-abortable-preclean-start]</span><br><span class="line">2024-06-06T20:39:22.796+0800: 1.313: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] </span><br><span class="line">2024-06-06T20:39:22.797+0800: 1.313: [GC (CMS Final Remark) [YG occupancy: 19865 K (92160 K)]2024-06-06T20:39:22.797+0800: 1.313: [Rescan (parallel) , 0.0003616 secs]2024-06-06T20:39:22.797+0800: 1.313: [weak refs processing, 0.0000255 secs]2024-06-06T20:39:22.797+0800: 1.313: [class unloading, 0.0005809 secs]2024-06-06T20:39:22.798+0800: 1.314: [scrub symbol table, 0.0002738 secs]2024-06-06T20:39:22.798+0800: 1.314: [scrub string table, 0.0000782 secs][1 CMS-remark: 88612K(102400K)] 108478K(194560K), 0.0013943 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] </span><br><span class="line">2024-06-06T20:39:22.798+0800: 1.315: [CMS-concurrent-sweep-start]</span><br><span class="line">2024-06-06T20:39:22.798+0800: 1.315: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] </span><br><span class="line">2024-06-06T20:39:22.798+0800: 1.315: [CMS-concurrent-reset-start]</span><br><span class="line">2024-06-06T20:39:22.799+0800: 1.316: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</span><br></pre></td></tr></table></figure>

<p>每条日志主要包含以下部分：GC发生时间；GC类型-GC原因；各区域GC收集器类型-GC区域回收情况；堆GC回收情况；GC耗时</p>
<p>GC类型包括GC（指Young GC）和Full GC</p>
<p>GC原因较多，主要的有：</p>
<ul>
<li>Allocation Failure，表示因对象分配失败而发生GC——其实一般来说，除非是测试代码中特意制造的场景，大多不会出现在Full GC，如果出现则意味着存在巨大的问题（内存泄漏？大对象？堆内存配置很小？）</li>
<li>CMS，CMS的详细过程，根据其原理，重点需要关注的是CMS Initial Mark和CMS Final Remark两个阶段——这两个阶段是需要STW的</li>
<li>Promotion Failure，年轻代存活对象晋升老年代时没有足够空间引起的GC</li>
<li>METADATA GC THREADHOLD，方法区内存不足引起。默认方法区空间是自动调整、自动扩展的，可以通过MaxMetaSpaceSize限制其扩展上限。如果出现了此类GC，关键的问题就是classloader不断加载class，基本是动态加载类引起的</li>
<li>System，手动调用System.gc()引起的GC操作</li>
</ul>
<p>各区域GC收集器类型，除了老年代最常用的CMS，常见的还有：</p>
<ul>
<li>DefNew，表示Default New Generation，指年轻代使用的Serial收集器</li>
<li>ParNew，表示Parallel New Generation，指年轻代使用的ParNew收集器</li>
<li>PSYoungGen，指年轻代使用的Parallel Scavenage</li>
<li>ParOldGen，指老年代使用的Parallel Old收集器</li>
<li>garbage-fisrt heap，使用G1收集器时出现</li>
</ul>
<p>以此为例：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2024-06-06T20:39:22.711+0800: 1.227: [GC (Allocation Failure) 2024-06-06T20:39:22.711+0800: 1.228: [ParNew: 81920K-&gt;9789K(92160K), 0.0200098 secs] 81920K-&gt;28238K(194560K), 0.0205668 secs] [Times: user=0.03 sys=0.05, real=0.02 secs]</span><br></pre></td></tr></table></figure>

<p>GC回收情况，类似：[ParNew: 81920K-&gt;9789K(92160K), 0.0200098 secs]，分别表示：[年轻代、ParNew收集器: 使用回收前内存占用-&gt;回收后内存占用(此内存区域最大容量), GC耗时]</p>
<p>而：81920K-&gt;28238K(194560K), 0.0205668 secs，表示：回收前堆内存占用-&gt;回收后堆内存占用(堆最大容量)（当然，这里的堆最大容量194560K &#x3D; 190M，而指定的Xmn为100M，这里和jmap&#x2F;jstat的输出一样，是统计口径的问题，from&#x2F;to区只会使用到一个，因此堆最大可用空间为200-10&#x3D;190M）；类似的，Metaspace的GC情况同理</p>
<p>结合年轻代回收量和堆整体回收量来看，这次回收中年轻代回收了81920K-9789K &#x3D; 72031K，堆整体占用减少了81920K-28238K &#x3D; 53682K，这说明年轻代有72031K-53682K &#x3D; 18349K晋升到了老年代</p>
<p>GC耗时，如上例的：[Times: user&#x3D;0.03 sys&#x3D;0.05, real&#x3D;0.02 secs]，user-用户态耗时，sys-内核态耗时，real-这次GC中客观世界经历的时长</p>
<p>如何理解real？举个例子，假如GC使用2个线程同一时刻开始并行执行回收，分别使用了0.1s和0.2s完成，那么user+sys为0.3s，但是real为0.2s</p>
<h3 id="常见GC问题"><a href="#常见GC问题" class="headerlink" title="常见GC问题"></a>常见GC问题</h3><p>一个曾遇到的情况：gc log显示，full gc频繁，并且full gc过程回收的空间比较大。这是很不合理的情况，老年代本身定位于存储存活时间长的对象，那为何一次gc会会收大量对象呢？我们知道，年轻代对象除了经过多次GC存活后晋升到老年代之外，如果年轻代容量不足，会有老年代提供分配担保，将不足以容纳的对象移入老年代；所以，出现这种情况，可以尝试增大年轻代空间。多大合适？full gc后老年代存活对象占用空间可以认为是老年代活跃对象的占用空间，一个经验值，将老年代容量设置为活跃对象占用的3倍为宜；除此之外的空间，可以全部给到年轻代</p>
<h3 id="GC-real很高？"><a href="#GC-real很高？" class="headerlink" title="GC real很高？"></a>GC real很高？</h3><p>有些情况下，我们会遇到一种情况：GC log显示的sys、user耗时都很短，但是real却很高</p>
<p>普通的业务应用可能比较少遇到这种情况，但是在大数据等场景下，相信做过排查的朋友一定有印象。典型的一个原因就是，服务器IO负载极高，导致GC log写出到磁盘耗时显著增加（是的，写gc log不是异步的，至少在jdk8的版本中不是），即阻塞时间增加，导致real time很高。这种情况在一般的业务系统相信很难出现，也很难通过模拟的方式来产生这种级别的IO负载；在大数据场景下，极重的shuffle等IO密集场景+flink实时任务等高敏感任务，会成为大问题</p>
<h2 id="使用GDB分析JVM内存"><a href="#使用GDB分析JVM内存" class="headerlink" title="使用GDB分析JVM内存"></a>使用GDB分析JVM内存</h2><p>堆内存出现问题，分析gc、分析dump文件等，容易操作。但是堆外内存出现问题时，排查的复杂性就大大高了</p>
<p>遇到过一个现象：java进程在操作系统的内存占用中，RSS远远超出了堆内存Xmx，此时往往意味着问题在于堆外内存。</p>
<p>这种情况下，java 8引入的jcmd工具可以使用-XX:NativeMemoryTracking&#x3D;detail参数查看Native memory的情况，可以一定程度上帮助排查</p>
<p>linux的gdb、pmap工具，可以对进程的内存进行dump，分析可疑的内存块；strace工具可以设置追踪内存分配相关的调用进行观察。</p>
<p>这类工具并不适合用于日常性普通问题的排查——换言之，需要用上这类工具，都是极其复杂、深入的问题，没有对OS、JVM足够的了解，没有足够的经验支撑，是很难进行的</p>
<h2 id="Arthas的使用"><a href="#Arthas的使用" class="headerlink" title="Arthas的使用"></a>Arthas的使用</h2><p>arthas的安装和启动参考官方文档。这里对使用arthas内存相关的命令简单演示</p>
<p>启动arthas后attach到java进程，使用dashboard命令查看JVM基本的线程、内存、运行环境等信息：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dashboard</span><br></pre></td></tr></table></figure>

<p>输出类似：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">ID NAME                GROUP     PRIORI STATE  %CPU  DELTA_ TIME   INTER DAEMON </span><br><span class="line">54 System Clock        main      5      TIMED_ 0.0   0.000  224:36 false true   </span><br><span class="line">-1 VM Periodic Task Th -         -1     -      0.0   0.000  8:26.3 false true   </span><br><span class="line">19 lettuce-timer-3-1   main      5      TIMED_ 0.0   0.000  3:44.8 false true   </span><br><span class="line">17 Catalina-utility-2  main      1      TIMED_ 0.0   0.000  0:45.0 false false  </span><br><span class="line">16 Catalina-utility-1  main      1      WAITIN 0.0   0.000  0:44.1 false false  </span><br><span class="line">-1 C2 CompilerThread1  -         -1     -      0.0   0.000  0:36.2 false true   </span><br><span class="line">35 http-nio-8090-Polle main      5      RUNNAB 0.0   0.000  0:32.5 false true   </span><br><span class="line">-1 C2 CompilerThread0  -         -1     -      0.0   0.000  0:31.8 false true   </span><br><span class="line">-1 VM Thread           -         -1     -      0.0   0.000  0:23.2 false true   </span><br><span class="line">43 mysql-cj-abandoned- main      5      TIMED_ 0.0   0.000  0:17.4 false true   </span><br><span class="line">Memory           used  total max  usage GC                                      </span><br><span class="line">heap             153M  494M  494M       gc.parnew.count     348                 </span><br><span class="line">par_eden_space   86M   136M  136M       gc.parnew.time(ms)  1237                </span><br><span class="line">par_survivor_spa 17M   17M   17M  100.0 gc.concurrentmarksw 5                   </span><br><span class="line">ce                                0%    eep.count                               </span><br><span class="line">cms_old_gen      50M   341M  341M       gc.concurrentmarksw 594                 </span><br><span class="line">nonheap          176M  185M  -1         eep.time(ms)                            </span><br><span class="line">code_cache       58M   58M   240M                                               </span><br><span class="line">Runtime                                                                         </span><br><span class="line">os.name                                 Linux                                   </span><br><span class="line">os.version                              3.10.0-1160.90.1.el7.x86_64             </span><br><span class="line">java.version                            1.8.0_351</span><br></pre></td></tr></table></figure>

<p>使用memory命令可以查看内存信息：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">memory</span><br></pre></td></tr></table></figure>

<p>输出与dashboard中内存信息一致：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Memory                            used        total      max         usage      </span><br><span class="line">heap                              167M        494M       494M        33.86%     </span><br><span class="line">par_eden_space                    100M        136M       136M        73.51%     </span><br><span class="line">par_survivor_space                17M         17M        17M         100.00%    </span><br><span class="line">cms_old_gen                       50M         341M       341M        14.69%     </span><br><span class="line">nonheap                           176M        186M       -1          94.94%     </span><br><span class="line">code_cache                        58M         58M        240M        24.29%     </span><br><span class="line">metaspace                         106M        113M       -1          93.41%     </span><br><span class="line">compressed_class_space            12M         13M        1024M       1.19%      </span><br><span class="line">direct                            80K         80K        -           100.00%    </span><br><span class="line">mapped                            0K          0K         -           0.00%</span><br></pre></td></tr></table></figure>

<p>其中：</p>
<ul>
<li>heap，堆内存</li>
<li>par_eden_space，eden区，这里年轻代使用的是ParNew收集器</li>
<li>par_survivor_space，form&#x2F;to区</li>
<li>cms_old_gen，老年代，这里使用的cms收集器</li>
<li>nonheap，非堆内存（栈、方法区等）</li>
<li>code_cache，JIT使用的代码缓存区域</li>
<li>metaspace，方法区</li>
<li>compressed_class_space，压缩类空间</li>
<li>direct，堆外direct直接内存（JNI、ByteBuffer等使用）</li>
<li>mapped，堆外Mapped memory(使用mmap)</li>
</ul>
<p>可以观察到，nonheap 约等于 code_cache + metaspace + compressed_class_space + direct + mapped ——对照<a target="_blank" rel="noopener" href="https://github.com/alibaba/arthas/blob/arthas-all-3.4.3/core/src/main/java/com/taobao/arthas/core/command/monitor200/DashboardCommand.java">arthas源代码</a>，这里的nonheap与之后几项也的确是总-分关系</p>
<p>使用heapdump可以dump内存到文件</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">heapdump</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">使用--live指定只导出存活对象</span></span><br><span class="line">heapdump --live</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Dumping heap to /tmp/heapdump2024-06-13-21-20566951954109687382.hprof ...</span><br><span class="line">Heap dump file created</span><br></pre></td></tr></table></figure>

<p>arthas的功能远比以上示例多得多，实际上，相比于查看JVM运行状态，可能对类、方法的调用调试功能更为有用</p>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/Java/" rel="tag"># Java</a>
              <a href="/tags/JVM/" rel="tag"># JVM</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/e72717fd.html" rel="prev" title="三方应用接入与OAuth的思考">
      <i class="fa fa-chevron-left"></i> 三方应用接入与OAuth的思考
    </a></div>
      <div class="post-nav-item">
    <a href="/69a9ed6b.html" rel="next" title="禅道ZenTao项目管理系统源码包+nginx部署">
      禅道ZenTao项目管理系统源码包+nginx部署 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#jmap%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="nav-number">1.</span> <span class="nav-text">jmap的使用</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#jstat%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="nav-number">2.</span> <span class="nav-text">jstat的使用</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#GC%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90"><span class="nav-number">3.</span> <span class="nav-text">GC日志分析</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%B8%B8%E8%A7%81GC%E9%97%AE%E9%A2%98"><span class="nav-number">3.1.</span> <span class="nav-text">常见GC问题</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#GC-real%E5%BE%88%E9%AB%98%EF%BC%9F"><span class="nav-number">3.2.</span> <span class="nav-text">GC real很高？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8GDB%E5%88%86%E6%9E%90JVM%E5%86%85%E5%AD%98"><span class="nav-number">4.</span> <span class="nav-text">使用GDB分析JVM内存</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Arthas%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="nav-number">5.</span> <span class="nav-text">Arthas的使用</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">马天</p>
  <div class="site-description" itemprop="description">程序猿</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">41</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">17</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">25</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/matian2014" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;matian2014" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:matian1103@qq.com" title="E-Mail → mailto:matian1103@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        
  <div class="beian"><a href="https://beian.miit.gov.cn/" rel="noopener" target="_blank">陕ICP备2023001563号 </a>
  </div>

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2025</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">马天</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://muse.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/muse.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

</body>
</html>
